#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

#include <linux/fs_context.h>
#include <linux/namei.h>
#include <linux/fs.h>

#define SFS_MAGIC		0xe7e98f24	/* some random number */

static const struct inode_operations sfs_dir_inode_operations;

static ssize_t sfs_read(struct file *file, char __user *buf,
			 size_t count, loff_t *ppos)
{
	struct qstr *name;
	ssize_t ret;

	if (*ppos)
		return 0;

	name = &file->f_path.dentry->d_name;
	ret = snprintf(buf, count, "My name is '%.*s'\n", name->len, name->name);
	*ppos += ret;

	return ret;
}

const struct file_operations sfs_file_operations = {
	.read = sfs_read
};

static struct inode *sfs_get_inode(struct super_block *sb, const struct inode *dir, umode_t mode, dev_t dev)
{
	struct inode *inode = new_inode(sb);

	inode->i_ino = get_next_ino();
	inode_init_owner(&nop_mnt_idmap, inode, dir, mode);

	switch (mode & S_IFMT) {
	default:
		init_special_inode(inode, mode, dev);
		break;
	case S_IFREG:
		inode->i_fop = &sfs_file_operations;
		break;
	case S_IFDIR:
		inode->i_op = &sfs_dir_inode_operations;
		inode->i_fop = &simple_dir_operations;
		inc_nlink(inode);
		break;
	}

	return inode;
}

static int sfs_mknod(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
{
	struct inode *inode = sfs_get_inode(dir->i_sb, dir, mode, dev);

	d_instantiate(dentry, inode);
	dget(dentry);

	return 0;
}

static int sfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
		       struct dentry *dentry, umode_t mode)
{
	int retval = sfs_mknod(NULL, dir, dentry, mode | S_IFDIR, 0);
	if (!retval)
		inc_nlink(dir);
	return retval;
}

static int sfs_create(struct mnt_idmap *idmap, struct inode *dir,
			struct dentry *dentry, umode_t mode, bool excl)
{
	return sfs_mknod(NULL, dir, dentry, mode | S_IFREG, 0);
}

static const struct inode_operations sfs_dir_inode_operations = {
	.create		= sfs_create,
	.mkdir		= sfs_mkdir,
	.mknod		= sfs_mknod,
	.lookup		= simple_lookup,
};

static int sfs_fill_super(struct super_block *sb, struct fs_context *fc)
{
	struct inode *inode;

	sb->s_magic = SFS_MAGIC;

	inode = sfs_get_inode(sb, NULL, S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO, 0);
	sb->s_root = d_make_root(inode);

	return 0;
}

static int sfs_get_tree(struct fs_context *fc)
{
	return get_tree_nodev(fc, sfs_fill_super);
}

static const struct fs_context_operations sfs_context_ops = {
	.get_tree	= sfs_get_tree,
};

static int sfs_init_fs_context(struct fs_context *fc)
{
	fc->ops = &sfs_context_ops;
	return 0;
}

static struct file_system_type sfs_fs_type = {
	.name		= "sfs",
	.init_fs_context = sfs_init_fs_context,
	.kill_sb	= kill_litter_super,
};

static int __init sfs_init(void)
{
    register_filesystem(&sfs_fs_type);
    return 0;
}

static void __exit sfs_exit(void)
{
    unregister_filesystem(&sfs_fs_type);
}

module_init(sfs_init);
module_exit(sfs_exit);
MODULE_LICENSE("GPL");
